package com.netx.generics.basic;
import com.netx.generics.translation.Results;
import com.netx.generics.translation.TranslationStep;
import com.netx.generics.util.Tools;
import com.netx.generics.util.Type;
import com.netx.generics.util.Builtins;
import com.netx.generics.util.BuiltinException;
import com.netx.generics.util.ConstructionException;
import java.lang.reflect.Method;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;


class ContextAnalyzer extends TranslationStep {

	private SymbolRoot _root = null;
	
	public ContextAnalyzer(ContextParser parser) {
		super(parser);
	}

	public Object performWork(Results r) {
		try {
			_root = (SymbolRoot)r.getContents();
			// Analyze namespaces:
			_getNamespaces(r, _root.pNamespaces);
			// Analyze contexts:
			_getContext(r, _root.sRootCtx, null, null);
			return Context.getRootContext();
		}
		catch(Throwable t) {
			r.addError("unexpected error: "+t);
			return null;
		}
	}
	
	private void _getNamespaces(Results r, List<SymbolNamespace> namespaces) {
		final String where = "namespace.package";
		Iterator<SymbolNamespace> it = namespaces.iterator();
		while(it.hasNext()) {
			SymbolNamespace ns = it.next();
			try {
				Checker.checkIdentifier(ns.pPackageName, where, true);
				Checker.checkIdentifier(ns.pAliasName, where, false);
				if(_root.aNamespaces.put(ns.pAliasName, ns.pPackageName) != null) {
					addError(r, where, "duplicated alias '"+ns.pAliasName+"'");
				}
			}
			catch(IllegalArgumentException iae) {
				String msg = iae.getMessage();
				msg = msg.substring(msg.indexOf(":")+2);
				addError(r, where, msg);
				it.remove();
			}
		}
	}
	
	private void _getContext(Results r, SymbolContext sContext, Context parent, String where) {
		Context ctx = null;
		String where2 = null;
		if(parent == null) {
			ctx = Context.getRootContext();
			where2 = "root";
		}
		else {
			sContext.aName = _checkName(r, sContext.pName, sContext.pName);
			ctx = parent.getContext(sContext.aName);
			if(ctx == null) {
				ctx = new Context(sContext.aName, parent);
			}
			where2 = where+"/"+ctx.getName();
		}
		// load properties:
		for(SymbolProperty sProp : sContext.properties) {
			Property prop = _getProperty(r, sProp, where2);
			if(prop != null) {
				try {
					ctx.setProperty(prop);
				}
				catch(ValidationException ve) {
					addError(r, where2+"/"+prop.getName(), ve.getMessage());
				}
			}
		}
		// load sub-contexts:
		Iterator<SymbolContext> itCtx = sContext.contexts.iterator();
		while(itCtx.hasNext()) {
			_getContext(r, itCtx.next(), ctx, where2);
		}
	}

	private Property _getProperty(Results r, SymbolProperty sProp, String where) {
		sProp.aName = _checkName(r, sProp.pName, sProp.pName);
		String where2 = where+"/"+sProp.pName;
		_loadHolder(r, sProp, where2);
		return new Property(sProp.aName, sProp.aValue);
	}
	
	private void _loadHolder(Results r, SymbolHolder holder, String where) {
		// load class:
		holder.aClass = _getClass(r, holder.pClassName, where);
		if(holder.aClass == null) {
			return;
		}
		// load value:
		if(holder.value != null && holder.createArgs.size() > 0) {
			addError(r, where, "both direct-value and create element used");
		}
		// try to create a direct value:
		if(holder.value != null) {
			holder.aValue = _getObject(r, holder.aClass, holder.value, holder.pIsArray, where);
		}
		else {
			// from create args:
			// if it doesn't have any, it will use the no-arg constructor:
			holder.aValue = _getObject(r, holder.aClass, holder.createArgs, holder.properties, holder.pIsArray, where);
		}
		// set object properties:
		if(holder.pIsArray) {
			// no properties can be used for an array:
			if(holder.properties.size() > 0) {
				addError(r, where, "properties cannot be used for an array");
			}
		}
		if(holder.aValue != null && holder.aClass != null) {
			for(SymbolProperty sProp : holder.properties) {
				Property prop = _getProperty(r, sProp, where);
				if(prop != null && sProp.aClass != null) {
					String where2 = where+"."+sProp.pName;
					String methodName = _translateName(sProp.aName);
					// invoke setter method:
					try {
						Method propertySetter = holder.aClass.getMethod(methodName, sProp.aClass);
						propertySetter.invoke(holder.aValue, prop.objectValue());
					}
					catch(NoSuchMethodException nsme) {
						addError(r, where2, "method "+methodName+"("+sProp.aClass.getName()+") not found");
					}
					catch(IllegalAccessException ite) {
						addError(r, where2, "property setter is not public");
					}
					catch(InvocationTargetException ite) {
						addError(r, where2, "error calling property setter: "+ite.getMessage());
					}
				}
			}
		}
	}
	
	private Object _getObject(Results r, Class<?> clazz, SymbolDirectValue sValue, boolean isArray, String where) {
		Class<?> valueClass = String.class;
		// Parse built-in expressions:
		List<String> parsedExprs = new ArrayList<String>();
		for(String expr : sValue.pValues) {
			try {
				parsedExprs.add(Builtins.evaluate(expr));
			}
			catch(BuiltinException be) {
				addError(r, where, be.getMessage());
			}
		}
		sValue.aValues = parsedExprs.toArray(new String[0]);
		// Treat type cast:
		if(sValue.typeCast != null) {
			valueClass = _getClass(r, sValue.typeCast, where+" (type cast)");
			if(valueClass == null) {
				return null;
			}
		}
		// if primitive type, assume the Object counterpart's type:
		valueClass = new Type(clazz).getObjectClass();
		if(isArray) {
			// translate from array type to non-array type:
			while(valueClass.isArray()) {
				valueClass = valueClass.getComponentType();
			}
			if(valueClass == String.class) {
				return sValue.aValues;
			}
			else {
				Object[] array = (Object[])Array.newInstance(valueClass, sValue.aValues.length);
				for(int i=0; i<array.length; i++) {
					try {
						Object value = Tools.createObject(valueClass, new String[] {sValue.aValues[i]});
						Array.set(array, i, value);
					}
					catch(ConstructionException ce) {
						addError(r, where+".value["+"]", ce.getMessage());
					}
					catch(IllegalArgumentException iae) {
						// not supposed to happen:
						throw new IllegalArgumentException(iae);
					}
				}
				return array;
			}
		}
		else {
			if(sValue.aValues.length > 1) {
				addError(r, where, "non-array type with array 'value' attribute");
			}
			if(valueClass == String.class) {
				return sValue.aValues[0];
			}
			else {
				try {
					return Tools.createObject(valueClass, new String[] {sValue.aValues[0]});
				}
				catch(ConstructionException ce) {
					addError(r, where, ce.getMessage());
					return null;
				}
			}
		}
	}
	
	private Object _getObject(Results r, Class<?> clazz, List<SymbolArgument> argList, List<SymbolProperty> propList, boolean isArray, String where) {
		clazz = new Type(clazz).getObjectClass();
		// load object:
		Class<?>[] argClasses = new Class[argList.size()];
		Object[] args = new Object[argList.size()];
		boolean nullArgClasses = false;
		Iterator<SymbolArgument> itArgs = argList.iterator();
		for(int i=0; itArgs.hasNext(); i++) {
			SymbolArgument sArg = itArgs.next();
			_loadHolder(r, sArg, where+".argument["+i+"]");
			argClasses[i] = sArg.aClass;
			if(sArg.aClass == null) {
				nullArgClasses = true;
			}
			args[i] = sArg.aValue;
		}
		if(isArray) {
			while(clazz.isArray()) {
				clazz = clazz.getComponentType();
			}
			Object[] array = (Object[])Array.newInstance(clazz, args.length);
			for(int i=0; i<args.length; i++) {
				try {
					Array.set(array, i, args[i]);
				}
				catch(IllegalArgumentException iae) {
					addError(r, where+".argument["+i+"]", "invalid argument type: "+args[i].getClass().getName());
				}
			}
			return array;
		}
		else {
			if(nullArgClasses == false) {
				try {
					return Tools.createObject(clazz, argClasses, args);
				}
				catch(ConstructionException ce) {
					addError(r, where, ce.getMessage());
					return null;
				}
			}
			else {
				return null;
			}
		}
	}
	
	private Class<?> _getClass(Results r, String className, String where) {
		// Alias:
		if(className.startsWith("@")) {
			int index = className.indexOf(".");
			if(index == -1) {
				addError(r, where, "expected class name after alias");
				return null;
			}
			String alias = className.substring(1, index);
			String packageName = _root.aNamespaces.get(alias);
			if(packageName == null) {
				addError(r, where, "alias '"+alias+"' not found");
				return null;
			}
			else {
				className = packageName + className.substring(index);
			}
		}
		try {
			return new Type(className).getBaseClass();
		}
		catch(IllegalArgumentException iae) {
			addError(r, where, iae.getMessage());
			return null;
		}
	}
	
	private String _checkName(Results r, String name, String where) {
		try {
			Checker.checkTextIdentifier(name, where+".name", true);
			return name;
		}
		catch(IllegalArgumentException iae) {
			r.addError(iae.getMessage());
			return name;
		}
	}
	
	private String _translateName(String name) {
		String[] split = name.split("[-]");
		StringBuilder result = new StringBuilder("set");
		for(int i=0; i<split.length; i++) {
			StringBuilder part = new StringBuilder(split[i]);
			part.setCharAt(0, Character.toUpperCase(part.charAt(0)));
			result.append(part);
		}
		return result.toString();
	}
}
